/*
OpenMVL Matrix Vector Library
Copyright (c) 2009 FluidInteractive (R)
 
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising
from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
 
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
 
Written by: Nicola Candussi <nicola@fluidinteractive.com>
*/

//expr_binary.hpp

#ifndef MVL_EXPR_BINARY_HPP
#define MVL_EXPR_BINARY_HPP

#include "expr.hpp"
#include "promote_traits.hpp"
#include "mat_mat_prod.hpp"
#include "mat_mat_cross.hpp"
#include "element_binary.hpp"
#include "expr_scalar.hpp"

namespace mvl {

template<typename E1, typename E2, typename F>
class expr_binary : public expr< expr_binary<E1, E2, F> > 
{ 
public:
    typedef expr_binary self_t;

    typedef E1 expr1_t;
    typedef E2 expr2_t;

private:
    typedef F functor_t;

public:
    typedef typename functor_t::layout_t    layout_t;
    typedef typename functor_t::value_type  value_type;
    typedef typename layout_t::order       order;
    typedef typename layout_t::Rows        Rows;
    typedef typename layout_t::Cols        Cols;
    typedef typename layout_t::Size        Size;

    typedef typename functor_t::expr1_closure_t expr1_closure_t; 
    typedef typename functor_t::expr2_closure_t expr2_closure_t; 

    typedef const self_t const_closure_t;
    typedef const_closure_t closure_t;

public:
    expr_binary(expr1_t const& e1, expr2_t const& e2) : m_e1(e1), m_e2(e2) { };

   // using expr<self_t>::operator();

    //access
    template<typename Entry>
    value_type at() const {
        return functor_t::template at<Entry>(m_e1, m_e2);
    }

private:
    expr1_closure_t m_e1;
    expr2_closure_t m_e2;
};

template<typename E1, typename E2>
struct prod_traits 
{
    typedef const expr_binary<
        const E1, const E2,  
        mat_mat_prod<const E1, const E2> 
    > type;
};

//prod
template<typename E1, typename E2>
inline 
typename prod_traits<E1, E2>::type
prod(expr<E1> const& e1, expr<E2> const& e2) {
    MVL_STATIC_CHECK((E1::Cols::value == E2::Rows::value), "mat_size_mismatch");
    typedef typename prod_traits<E1, E2>::type expr_t;
    return expr_t(e1(), e2());
}

//operator +
template<typename E1, typename E2>
struct operator_add_traits 
{
    typedef const expr_binary<
        E1, E2,
        element_binary<E1, E2, fn_add, tagfn_add>
    > type;
};

template<typename E1, typename E2>
inline 
typename operator_add_traits<E1, E2>::type
operator + (expr<E1> const& e1, expr<E2> const& e2) {
    MVL_STATIC_CHECK((E1::Rows::value == E2::Rows::value), "mat_size_mismatch");
    MVL_STATIC_CHECK((E1::Cols::value == E2::Cols::value), "mat_size_mismatch");
    typedef typename operator_add_traits<E1, E2>::type expr_t;
    return expr_t(e1(), e2());
}

//operator -
template<typename E1, typename E2>
struct operator_sub_traits
{
    typedef const expr_binary<
        E1, E2,
        element_binary<E1, E2, fn_sub, tagfn_sub>
    > type;
};

template<typename E1, typename E2>
inline 
typename operator_sub_traits<E1, E2>::type
operator - (expr<E1> const& e1, expr<E2> const& e2) {
    MVL_STATIC_CHECK((E1::Rows::value == E2::Rows::value), "mat_size_mismatch");
    MVL_STATIC_CHECK((E1::Cols::value == E2::Cols::value), "mat_size_mismatch");
    typedef typename operator_sub_traits<E1, E2>::type expr_t;
    return expr_t(e1(), e2());
}

//element_mul
template<typename E1, typename E2>
struct element_mul_traits 
{
    typedef const expr_binary<
        E1, E2,
        element_binary<E1, E2, fn_mul, tagfn_mul>
    > type;
};

template<typename E1, typename E2>
inline 
typename element_mul_traits<E1, E2>::type
element_mul(expr<E1> const& e1, expr<E2> const& e2) {
    MVL_STATIC_CHECK((E1::Rows::value == E2::Rows::value), "mat_size_mismatch");
    MVL_STATIC_CHECK((E1::Cols::value == E2::Cols::value), "mat_size_mismatch");
    typedef typename element_mul_traits<E1, E2>::type expr_t;
    return expr_t(e1(), e2());
}

//element_div
template<typename E1, typename E2>
struct element_div_traits
{
    typedef const expr_binary<
        E1, E2,
        element_binary<E1, E2, fn_div, tagfn_div>
    > type;
};

template<typename E1, typename E2>
inline
typename element_div_traits<E1, E2>::type
element_div(expr<E1> const& e1, expr<E2> const& e2) {
    MVL_STATIC_CHECK((E1::Rows::value == E2::Rows::value), "mat_size_mismatch");
    MVL_STATIC_CHECK((E1::Cols::value == E2::Cols::value), "mat_size_mismatch");
    typedef typename element_div_traits<E1, E2>::type expr_t;
    return expr_t(e1(), e2());
}

// operator * 
template<typename E, typename T>
struct operator_mul1_traits {
    typedef E expr1_t;
    typedef const expr_scalar<T, typename E::Rows, typename E::Cols, layout::row_major_tag> expr2_t;
    typedef const expr_binary<expr1_t, expr2_t, element_binary<expr1_t, expr2_t, fn_mul, tagfn_mul> > type;
};

template<typename E, typename T>
inline 
typename operator_mul1_traits<E, T>::type
operator * (expr<E> const& e, T value) {
    typedef typename operator_mul1_traits<E, T>::expr2_t expr2_t;
    typedef typename operator_mul1_traits<E, T>::type expr_t;
    return expr_t(e(), expr2_t(value));
}

template<typename E, typename T>
struct operator_mul2_traits {
    typedef const expr_scalar<T, typename E::Rows, typename E::Cols, layout::row_major_tag> expr1_t;
    typedef E expr2_t;
    typedef const expr_binary<expr1_t, expr2_t, element_binary<expr1_t, expr2_t, fn_mul, tagfn_mul> > type;
};

template<typename E, typename T>
inline 
typename operator_mul2_traits<E, T>::type
operator * (T value, expr<E> const& e) {
    typedef typename operator_mul2_traits<E, T>::expr1_t expr1_t;
    typedef typename operator_mul2_traits<E, T>::type expr_t;
    return expr_t(expr1_t(value), e());
}

//cross product
template<typename E1, typename E2>
struct cross_traits {
    typedef E1 expr1_t;
    typedef E2 expr2_t;
    typedef const expr_binary<expr1_t, expr2_t, mat_mat_cross<expr1_t, expr2_t> > type;
};

template<typename E1, typename E2>
inline 
typename cross_traits<E1, E2>::type
cross(expr<E1> const& e1, expr<E2> const& e2) {
    MVL_STATIC_CHECK((E1::Rows::value == E2::Rows::value), "vector_size_mismatch");
    MVL_STATIC_CHECK((E1::Cols::value == E2::Cols::value), "vector_size_mismatch");
    MVL_STATIC_CHECK(((E1::Cols::value == 1 && E1::Rows::value >= 3) &&
                      (E2::Cols::value == 1 && E2::Rows::value >= 3)), "vector_size_mismatch");
    typedef typename cross_traits<E1, E2>::type expr_t;
    return expr_t(e1(), e2());
}

} // namespace mvl

#endif
